package com.novunda.platform.web.config;

import com.google.common.collect.Lists;
import com.novunda.platform.common.dialects.shiro.ShiroDialect;
import com.novunda.platform.common.utils.PWDUtils;
import com.novunda.platform.security.*;
import com.novunda.platform.security.qrcode.QrCodeAuthenticatingFilter;
import com.novunda.platform.security.qrcode.QrCodeAuthorizingRealm;
import com.novunda.platform.security.qrcode.QrCodeCredentialsMatcher;
import nz.net.ultraq.thymeleaf.LayoutDialect;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.http.CacheControl;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.mvc.WebContentInterceptor;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring4.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ITemplateResolver;

import java.util.List;

/**
 * @author linfeng
 */
@Configuration
@ComponentScan(basePackages = {"com.novunda.platform.web"})
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter implements ApplicationContextAware {

    private static final Logger logger = LoggerFactory.getLogger(WebMvcConfig.class);
    private static final String UTF8 = "UTF-8";

    private ApplicationContext applicationContext;

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        super.addViewControllers(registry);
        registry.addViewController("/").setViewName("index");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @Bean
    public ViewResolver viewResolver() {
        ThymeleafViewResolver resolver = new ThymeleafViewResolver();
        resolver.setTemplateEngine(templateEngine());
        resolver.setCharacterEncoding(UTF8);
        return resolver;
    }

    @Bean
    public TemplateEngine templateEngine() {
        SpringTemplateEngine engine = new SpringTemplateEngine();
        engine.setEnableSpringELCompiler(true);
        engine.setTemplateResolver(templateResolver());
        engine.addDialect(new LayoutDialect());
        engine.addDialect(new ShiroDialect());
        return engine;
    }

    private ITemplateResolver templateResolver() {
        SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
        resolver.setApplicationContext(applicationContext);
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".html");
        resolver.setTemplateMode(TemplateMode.HTML);
        resolver.setOrder(1);
        resolver.setCacheable(false);
        resolver.setCharacterEncoding(UTF8);
        return resolver;
    }


    @Bean
    public ResourceBundleMessageSource resourceBundleMessageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("messages");
        messageSource.setCacheSeconds(60);
        return messageSource;
    }

    @Bean
    public RequestMappingHandlerAdapter mappingHandlerAdapter() {

        List<HttpMessageConverter<?>> messageConverters = Lists.newArrayList();
        messageConverters.add(new MappingJackson2HttpMessageConverter());
        messageConverters.add(new ByteArrayHttpMessageConverter());
        messageConverters.add(new SourceHttpMessageConverter());
        messageConverters.add(new FormHttpMessageConverter());
        messageConverters.add(new StringHttpMessageConverter());

        RequestMappingHandlerAdapter mappingHandlerAdapter = new RequestMappingHandlerAdapter();
        mappingHandlerAdapter.setCacheSeconds(0);
        mappingHandlerAdapter.setCacheControl(CacheControl.noCache());
        mappingHandlerAdapter.setMessageConverters(messageConverters);
        mappingHandlerAdapter.setIgnoreDefaultModelOnRedirect(true);
        return mappingHandlerAdapter;
    }

    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        logger.info("LocaleChangeInterceptor");
        return new LocaleChangeInterceptor();
    }

    @Bean(name = "localeResolver")
    public CookieLocaleResolver cookieLocaleResolver() {
        logger.info("CookieLocaleResolver");
        return new CookieLocaleResolver();
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/assets/**").addResourceLocations("/assets/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("/webjars/");
    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    @Bean(name = "multipartResolver")
    public CommonsMultipartResolver commonsMultipartResolver() {
        logger.info("CommonsMultipartResolver");
        return new CommonsMultipartResolver();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        WebContentInterceptor contentInterceptor = new WebContentInterceptor();
        contentInterceptor.addCacheMapping(CacheControl.noCache(), "/**");
        registry.addInterceptor(contentInterceptor);
    }

    //====================================================================


    @Bean(name = "sessionManager")
    public DefaultWebSessionManager sessionManager() {

        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionDAO(new EnterpriseCacheSessionDAO());
        sessionManager.setCacheManager(shiroCacheManager());
        return sessionManager;
    }

    @Bean(name = "shiroCacheManager")
    public EhCacheManager shiroCacheManager() {
        EhCacheManager ehCacheManager = new EhCacheManager();
        ehCacheManager.setCacheManagerConfigFile("classpath:cache/ehcache-shiro.xml");
        return ehCacheManager;
    }

    public AuthorizingRealm systemRealm() {

        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(PWDUtils.HASH_ALGORITHM);
        matcher.setHashIterations(PWDUtils.HASH_INTERATIONS);

        SystemAuthorizingRealm systemAuthorizingRealm = new SystemAuthorizingRealm();
        systemAuthorizingRealm.setCredentialsMatcher(matcher);

        return systemAuthorizingRealm;
    }

    @Bean(name = "securityManager")
    public DefaultWebSecurityManager securityManager() {

        QrCodeAuthorizingRealm qrCodeAuthorizingRealm = new QrCodeAuthorizingRealm();
        qrCodeAuthorizingRealm.setCredentialsMatcher(new QrCodeCredentialsMatcher());

        List<Realm> realms = Lists.newArrayList();
        realms.add(systemRealm());
        realms.add(qrCodeAuthorizingRealm);

        NovModularRealmAuthenticator realmAuthenticator = new NovModularRealmAuthenticator();
        realmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());

//        ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();
//        modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());

        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setCacheManager(shiroCacheManager());
        securityManager.setAuthenticator(realmAuthenticator);
        securityManager.setRealms(realms);
        securityManager.setSessionManager(sessionManager());

        return securityManager;
    }

    @Bean(name = "lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @DependsOn("lifecycleBeanPostProcessor")
    @Bean
    public DefaultAdvisorAutoProxyCreator autoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor advisor() {
        AuthorizationAttributeSourceAdvisor sourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        sourceAdvisor.setSecurityManager(securityManager());
        return sourceAdvisor;
    }

    public KickoutSessionControlFilter kickoutSessionControlFilter() {
        KickoutSessionControlFilter sessionControlFilter = new KickoutSessionControlFilter();
        sessionControlFilter.setSessionManager(sessionManager());
        sessionControlFilter.setCacheManager(shiroCacheManager());
        sessionControlFilter.setMaxSession(1);
        sessionControlFilter.setKickoutAfter(false);
        sessionControlFilter.setKickoutUrl("/login?kickout=1");
        return sessionControlFilter;
    }

    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean() {

        ShiroFilterFactoryBean shiroFilter = new MyShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager());
        shiroFilter.setLoginUrl("/login");
        shiroFilter.setSuccessUrl("/welcome");
        shiroFilter.setUnauthorizedUrl("/unauthorized");
        shiroFilter.getFilters().put("authc", new SystemFormAuthenticationFilter("/login"));
        shiroFilter.getFilters().put("qrcode", new QrCodeAuthenticatingFilter("/qrlogin"));
        shiroFilter.getFilters().put("kickout", kickoutSessionControlFilter());

        shiroFilter.setFilterChainDefinitions("" +
                "                /assets/** = anon\n" +
                "                /errors/** = anon\n" +
                "                /login/status = anon\n" +
                "                /cc/lpc = anon\n" +
                "                /userfiles/** = anon\n" +
                "                /bug/report = anon\n" +
                "                /sys/log/report = anon\n" +
                "                /query/user/getUserInfoByName = anon\n" +
                "				/authority/report/authorityReport = anon\n" +
                "                /sys/office/add = anon\n" +
                "                / = kickout,authc\n" +
                "                /qrlogin = kickout,qrcode\n" +
                "                /login = kickout,authc\n" +
                "                /logout = logout\n" +
                "                /esc/* = anon\n" +
                "                /olc/csi = anon\n" +
                "                /olc/rsi = anon\n" +
                "                /registerManage/rs = anon\n" +
                "                /** = kickout,authc");

        return shiroFilter;
    }

}
